﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Collections;

namespace TerenXNA
{
    public class World
    {
        // ustawienia
        public static bool tileObjectDraw = true;
        public static bool tileTerrainDraw = true;
        public static bool tileTterrainLoadLdTextures = true;
        public static bool tileTterrainLoadHdTextures = false;
        
        // sciezka do folderu swiata
        public static string path;
        // wymiary swiata [ilosc pól we wspolrzednych x i y]
        public static int tilesX, tilesY;

        public static bool isTextureVisible = true;
        public static bool isHeightMapVisible = false;
        public static bool isGridVisible = false;

        // promień widoczności
        //int uLod = 50;
        public int uLod = 20;

        // tablica pól swiata
        public Tile[,] tiles;

        // kamera
        public Camera aktCam;

        // niebo
        private Skybox skybox;
        //Color skyColour = new Color(215, 244, 251);
        //Color skyColour = new Color(42, 66, 120);
        Color skyColour = new Color(55, 86, 151);

        // tekst na ekranie
        private TextViewer textViewer;
        
        // menu na ekranie
        private ScreenMenu screenMenu;

        // obiekty naziemne
        public ArrayList worldObjects;

        // czy swiat wczytany
        public bool loaded = false;

        //dynamiczne swiatlo
        Vector3 lightDirection = new Vector3(1.0f, 1.0f, 1.0f);
        //float angleY = 0.01f;
        //float angleX = 0.01f;
        //float angleZ = 0.01f;
        public float[] Angles = { 0.0f, 0.0f, 0.0f };
        //Wczytanie świata
        public void Load(string arg)
        {
            TKolor.loadBitmaps();
            
            path = arg;
            string sh;

            // Wczytanie ustawien swiata
            FileLoader worldfile = new FileLoader(path + "/world.txt");
            Console.WriteLine(path + "world.txt");

            if (!worldfile.loaded)
            {
                if (File.Exists(path + "world.txt"))
                {
                    Console.WriteLine("Plik istnieje, ale nie można go odczytać.");
                }
                else
                {
                    Console.WriteLine("Nie znaleziono pliku.");
                }

                Game1.game1Ref.closeGame();
                return;
            }

            Console.WriteLine("Trwa wczytywanie...");

            sh = "TilesX";
            FparseXML.szukajsekcji(sh, worldfile);
            tilesX = (int)FparseXML.parsujr(worldfile);

            sh = "TilesY";
            FparseXML.szukajsekcji(sh, worldfile);
            tilesY = (int)FparseXML.parsujr(worldfile);

            sh = "TileWidth";
            FparseXML.szukajsekcji(sh, worldfile);
            //Tile.width = (int)FparseXML.parsujr(worldfile);

            sh = "TileHeight";
            FparseXML.szukajsekcji(sh, worldfile);
            //Tile.height = (int)FparseXML.parsujr(worldfile);

            // Utworzenie kawalkow swiata
            tiles = new Tile[tilesX, tilesY];
            for (int i = 0; i < tilesX; i++)
            {
                for (int j = 0; j < tilesY; j++)
                {
                    tiles[i, j] = new Tile(i, j);
                    tiles[i, j].load();
                    //tiles[i, j].worldObjects = new ArrayList();
                }
            }

            // załadowanie wszystkich obiektów na mapie
            loadWorldObjects();

            //Console.WriteLine(new CoordinateConverter().degrees2meters(54.371672932967, 18.612425449147).X);

            //utworzenie kamery
            //aktCam = new CameraDebug(this);
            aktCam = new CameraFly(this);
            // wspolrzedne kafla
            /*
            aktCam.coords.aktwx = 15;
            aktCam.coords.aktwz = 50;
            // wspolrzedne na kaflu
            aktCam.coords.oby = 100;
            aktCam.coords.obx = 570;
            aktCam.coords.obz = 320;
            // kat obrotu
            aktCam.coords.kato = -0.28f;
            aktCam.coords.katov = -0.5f;
            */
            aktCam.coords.aktwx = 15;
            aktCam.coords.aktwz = 49;
            // wspolrzedne na kaflu
            aktCam.coords.oby = 460;
            aktCam.coords.obx = 605;
            aktCam.coords.obz = 430;
            // kat obrotu
            aktCam.coords.kato = -0.08f;
            aktCam.coords.katov = -0.59f;
            Mouse.SetPosition(Convert.ToUInt16(System.Configuration.ConfigurationManager.AppSettings["ResolutionWidth"]) / 2,
                              Convert.ToUInt16(System.Configuration.ConfigurationManager.AppSettings["ResolutionHeight"]) / 2);

            //utworzenie nieba
            skybox = new Skybox();

            //inicjalizacja tekstu
            textViewer = new TextViewer();

            //inicjalizacja menu
            screenMenu = new ScreenMenu(this);
            //przypisanie aktualnego typu kamery do menu
            ScreenMenu.actualCameraMode = aktCam.cameraType;

            //swiat wczytany
            loaded = true;
        }

        public void keyUpdate(KeyboardState keyboardState, MouseState mouseState)
        {
            if (!loaded) return;

            if (keyboardState.IsKeyDown(Keys.Enter))
            {
                if (!screenMenu.DisplayGUIButtonPressed)
                {
                    toggleScreenMenu();
                }
                screenMenu.DisplayGUIButtonPressed = true;

            }
            else
            {
                screenMenu.DisplayGUIButtonPressed = false;
            }

            if (keyboardState.IsKeyDown(Keys.D1))
            {
                Camera.switchCamera(0, ref aktCam, mouseState);
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Fly;
            }
            else if (keyboardState.IsKeyDown(Keys.D2))
            {
                Camera.switchCamera(1, ref aktCam, mouseState);
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Ground;
            }
            else if (keyboardState.IsKeyDown(Keys.D3))
            {
                Camera.switchCamera(2, ref aktCam, mouseState);
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Static;
            }
            else if (keyboardState.IsKeyDown(Keys.D4))
            {
                Camera.switchCamera(3, ref aktCam, mouseState);
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Debug;
            }
            //update dla kamery
            aktCam.keyUpdate(keyboardState);
            //( tu dodawac kolejne )

            textViewer.keyUpdate(keyboardState);

            screenMenu.Update();
            
        }

        public void buttonUpdate(GamePadState gamepadState)
        {
            /*
            if (gamepadState.Buttons.Start == ButtonState.Pressed)
            {
                if (!screenMenu.DisplayGUIButtonPressed)
                {
                    toggleScreenMenu();
                }
                screenMenu.DisplayGUIButtonPressed = true;

            }
            else
            {
                screenMenu.DisplayGUIButtonPressed = false;
            }
            */

            if (gamepadState.DPad.Up == ButtonState.Pressed)
            {
                Camera.switchCamera(0, ref aktCam, Mouse.GetState());
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Fly;
            }
            else if (gamepadState.DPad.Down == ButtonState.Pressed)
            {
                Camera.switchCamera(1, ref aktCam, Mouse.GetState());
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Ground;
            }
            else if (gamepadState.DPad.Right == ButtonState.Pressed)
            {
                Camera.switchCamera(2, ref aktCam, Mouse.GetState());
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Static;
            }
            else if (gamepadState.DPad.Left == ButtonState.Pressed)
            {
                Camera.switchCamera(3, ref aktCam, Mouse.GetState());
                ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Debug;
            }

            //update dla kamery
            aktCam.buttonUpdate(gamepadState);
        }

        public void mouseUpdate(MouseState mouseState)
        {
            if (!loaded) return;
            
            //update dla kamery
            aktCam.mouseUpdate(mouseState);
        }

        private void toggleScreenMenu()
        {
            if (!screenMenu.Showed)
            {
                aktCam.CameraPause();
                Camera.switchCamera(3, ref aktCam, Mouse.GetState());
                //ScreenMenu.actualCameraMode = ScreenMenu.CameraType.Static;
            }
            else
            {
                aktCam.CameraResume();
                Camera.switchCamera((int)ScreenMenu.actualCameraMode, ref aktCam, Mouse.GetState());
            }

            screenMenu.Showed = !screenMenu.Showed;
        }

        public void prepareLight()
        {
            //if (lightDirection.Y <= -0.5f || lightDirection.Y >= 1.5f) angleY = -angleY;
            //if (lightDirection.X <= -1.5f || lightDirection.X >= 5.5f) angleX = -angleX;
            //if (lightDirection.Z <= -0.5f || lightDirection.Z >= 1.5f) angleZ = -angleZ;

            //lightDirection.Y += angleY * 0.5f;
            //lightDirection.X += angleX * 0.5f;
            //lightDirection.Z += angleZ * 0.5f;

            lightDirection.X = -Angles[0];
            lightDirection.Y = (0.8f - Math.Abs(Angles[0]) * 0.1f) * (0.9f + Game1.heightScale * 0.1f);
            lightDirection.Z = -1;
        }

        public void draw()
        {
            //sprawdzamy czy swiat wczytany i czy kamera wczytana
            //pamietac zeby kazdy obiekt majacy funkcje 'draw' mial 
            //sparwdzenie czy moze byc rysowany - koniecznie :)
            if (!loaded) return;
            if (!loaded) return;
            //sprawdzenie czy kamera wczytana, bo co to za swiat bez kamery
            if (!aktCam.loaded) return;

            //ustawienia grafiki
            //inicjalizacja
            //Game1.device.Clear(Color.CornflowerBlue);
            Game1.device.Clear(skyColour);

            // widok gracza
            Matrix projectionMatrix = aktCam.view();
            Matrix viewMatrix = Matrix.CreateLookAt(aktCam.getEye(), aktCam.getTarget(), aktCam.getUp());
            Matrix translateMatrix;

            Game1.effect.Parameters["xProjection"].SetValue(projectionMatrix);
            Game1.effect.Parameters["xWorld"].SetValue(Matrix.Identity);
            Game1.effect.Parameters["xView"].SetValue(viewMatrix);

            //oswietlenie
            //Vector3 lightDirection = new Vector3(Angles[0], Angles[1], Angles[2]);
            //lightDirection.Normalize();
            prepareLight();
            Game1.effect.Parameters["xLightDirection"].SetValue(lightDirection);
            Game1.effect.Parameters["xAmbient"].SetValue(0.2f); Game1.effect.Parameters["xEnableLighting"].SetValue(true);

            // render nieba
            skybox.draw(projectionMatrix, viewMatrix, aktCam, -lightDirection.X * 0.3f);

            //renderowanie swiata w promieniu 'uLod' od gracza
            //tu raczej zadne zmiany juz nie zajda
            for (int i = -uLod; i < uLod + 1; i++)
            {
                for (int j = -uLod; j < uLod + 1; j++)
                {
                    if (aktCam.coords.aktwx + i < 0 || aktCam.coords.aktwz + j < 0 || aktCam.coords.aktwx + i >= tilesX || aktCam.coords.aktwz + j >= tilesY) 
                        continue;

                    translateMatrix = Matrix.CreateTranslation((Tile.width - 1) * i, 0, (Tile.height - 1) * j) * Matrix.CreateScale(1, Game1.heightScale, 1);
                    Game1.effect.Parameters["xWorld"].SetValue(Matrix.Identity * translateMatrix);

                    tiles[aktCam.coords.aktwx + i, aktCam.coords.aktwz + j].draw((int)Math.Sqrt(i * i + j * j), viewMatrix, projectionMatrix, translateMatrix);
                }
            }

            // renderowanie obiektow
            if (Game1.showObjects)
            {
                if (World.tileObjectDraw && worldObjects != null)
                {
                    foreach (WorldObject worldObject in worldObjects)
                    {
                        worldObject.draw(viewMatrix, projectionMatrix, aktCam);
                    }
                }
            }

            // rysowanie tekstu
            textViewer.draw(aktCam.coords.obx + aktCam.coords.aktwx * 1024, aktCam.coords.obz + aktCam.coords.aktwz * 1024, aktCam.coords.oby, tilesX, tilesY, Tile.width, Tile.height);
            //Console.WriteLine("" + (aktCam.coords.obx + aktCam.coords.aktwx * 1024) + ", " + (aktCam.coords.obz + aktCam.coords.aktwz * 1024));
            
            screenMenu.Draw();
            Game1.device.BlendState = BlendState.Opaque;
            DepthStencilState depthState = new DepthStencilState();
            depthState.DepthBufferEnable = true; // Enable the depth buffer
            depthState.DepthBufferWriteEnable = true; // When drawing to the screen, write to the depth buffer
            Game1.device.DepthStencilState = depthState;
            
        }

        // funkcja ładująca wszystkie obiekty na mapie
        private void loadWorldObjects()
        {
            worldObjects = new ArrayList();

            if (Game1.showObjects)
            {
                double PGLatOffset = -0.0407;
                double PGLonOffset = -0.0226;

                addWorldObject("Models/ETI", 54.370968731794 + PGLatOffset, 18.613441544928 + PGLonOffset, 11);
                addWorldObject("Models/NETI", 54.371672932967 - 0.00006 + PGLatOffset, 18.612425449147 + 0.00041 + PGLonOffset, 11);
                addWorldObject("Models/Baraki", 54.370993024508 + 0.00005 + PGLatOffset, 18.613263605765 + 0.00018 + PGLonOffset, 11);
                //addWorldObject("Models/OiO", 54.372676360967 - 0.0001 + PGLatOffset, 18.613211730804 + 0.0003 + PGLonOffset, 11);
                addWorldObject("Models/InstMaszPrzep", 54.372882111945 - 0.0001 + PGLatOffset, 18.614879949027 - 0.0005 + PGLonOffset, 11);
                addWorldObject("Models/Mechanika", 54.371704964755 - 0.0001 + PGLatOffset, 18.615024460703 - 0.00045 + PGLonOffset, 11);
            }
        }

        // funkcja dodająca do świata obiekt o podanej nazwie i współrzędnych
        private void addWorldObject(String assetName, double latitude, double longitude, double height)
        {
            Model model = null;
            try
            {
                model = Game1.contentManager.Load<Model>(assetName);
            }
            catch (Exception)
            {
                //throw;
                Console.WriteLine("Nie znaleziono '" + assetName + "'");
                return;
            }
            
            //tiles[0, 0].worldObjects = new ArrayList();
            int tempX = 0, tempY = 0;
            Vector3 tempPosition = new CoordinateConverter().degrees2meters(latitude, longitude);
            tempX = (int)(tempPosition.X / (Tile.width - 1));
            tempY = (int)(tempPosition.Y / (Tile.width - 1));
            if (tempX < 0)
            {
                tempX = 0;
            }
            if (tempX >= tilesX)
            {
                tempX = tilesX - 1;
            }
            if (tempY < 0)
            {
                tempY = 0;
            }
            if (tempY >= tilesY)
            {
                tempY = tilesY - 1;
            }
            //tempX = tempY = 0;
            tempPosition.Y -= tempX * (Tile.width - 1);
            tempPosition.X -= tempY * (Tile.height - 1);
            tempPosition.Z = (float)height;
            //iles[tempX, tempY].worldObjects.Add(new WorldObject(model, tempPosition));
            //Console.WriteLine("Wczytano '" + assetName + "' na pozycję: " + tempPosition);
            Console.WriteLine("Dodano '" + assetName + "' na pozycję: [" + tempX + ", " + tempY + "]");
            worldObjects.Add(new WorldObject(model, tempPosition, tempX, tempY));
        }
    }
}
